#include "EnemyAI.h"

#include "LandPhysic.h"
#include "Balloon.h"
#include "Hero.h"

#include "NormalEnemyAI.h"
#include "StoneEnemyAI.h"
#include "BowEnemyAI.h"
#include "JavelinEnemyAI.h"


//------------------------------ static ------------------------------------------
EnemyAI* EnemyAI::createEnemyAI(ENEMY_TYPE eType, GamePlayScreen *pGameScreen, Enemy *pEnemy)
{
    EnemyAI *enemyAI = NULL;

    switch (eType) {
    case FOOL_ENEMY:
        enemyAI = new EnemyAI();
        break;

    case NORMAL_ENEMY:
        enemyAI = new NormalEnemyAI();
        break;

    case STONE_ENEMY:
        enemyAI = new StoneEnemyAI();
        break;

    case BOW_ENEMY:
        enemyAI = new BowEnemyAI();
        break;

    case JAVELIN_ENEMY:
        enemyAI = new JavelinEnemyAI();
        break;

    default:
        enemyAI = new NormalEnemyAI();
        break;
    }

    enemyAI->initEnemyAI(pGameScreen, pEnemy);
    return enemyAI;
}

// ------------------------------------- initial -------------------------------------
EnemyAI::EnemyAI()
{
}

EnemyAI::~EnemyAI()
{
}

void EnemyAI::initEnemyAI(GamePlayScreen *pGameScreen, Enemy *pEnemy)
{
    PhysicObject::initGameObjectWith(pGameScreen);

    regionList = new CCArray();
    trapList = new CCArray();

    this->createRegions();

    this->enemyInstance = pEnemy;
    this->sawHero = false;

    radios = NORMAL_RADIOS * ScreenManager::sharedScreenMan()->scaleFactor;
    topRegion1 = (SensorRegion*)regionList->objectAtIndex(UP_REGION-1);
    topRegion2 = (SensorRegion*)regionList->objectAtIndex(UP_REGION);
    curRegion = (SensorRegion*)regionList->objectAtIndex(UP_REGION);
    refreshTime = TIME_TO_REFRESH - level * 0.3;
    attackDuration = TIME_NORMAL_ATTACK - level*0.02;

    this->createPhysicBody();
}

void EnemyAI::createRegions()
{
    regionList->removeAllObjects();

    b2Vec2 dividedPoints[DIVIDED_POINTS];

    for (int i = 0; i < DIVIDED_POINTS; ++i) {
        dividedPoints[i] = b2Vec2(cos((2*i) * M_PI / DIVIDED_POINTS), sin((2*i) * M_PI / DIVIDED_POINTS));
    }
    // create circle regions
    SensorRegion *region;
    for (int i = 0; i < DIVIDED_POINTS; ++i) {
        region = new SensorRegion();

        if (i+1 < DIVIDED_POINTS) {
            region->initSensorRegion(dividedPoints[i], dividedPoints[i+1]);
        } else {
            region->initSensorRegion(dividedPoints[i], dividedPoints[0]);
        }
        regionList->addObject(region);
    }
}

// ------------------------------------------- physic --------------------------------------
void EnemyAI::createPhysicBody()
{
    // define body
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.userData = this;
    bodyDef.position = enemyInstance->physicBody->GetPosition();
    this->physicBody = handler->world->CreateBody(&bodyDef);

    // create fixture
    b2FixtureDef fixture;
    // create shape
    b2CircleShape circle;
    circle.m_radius = radios;
    fixture.shape = &circle;
    fixture.density = 0.0f;
    fixture.friction = 0.0f;
    fixture.restitution = 0.0f;
    fixture.isSensor = true;
    fixture.filter.groupIndex = enemyInstance->group;
    physicBody->CreateFixture(&fixture);
    physicBody->SetGravityScale(0.0f);
    physicBody->SetFixedRotation(true);

    // define joint
    b2RevoluteJointDef jointDef;
    jointDef.Initialize(physicBody, enemyInstance->physicBody, physicBody->GetWorldCenter());
    jointDef.collideConnected = false;
    handler->world->CreateJoint(&jointDef);
}

void EnemyAI::beginContact(b2Fixture *ownFixture, b2Fixture *otherFixture, b2Contact *contact)
{

}

void EnemyAI::endContact(b2Fixture *ownFixture, b2Fixture *otherFixture, b2Contact *contact)
{

}

//--------------------------------------------- AI -------------------------------------
/**
 * @brief EnemyAI::findRegionContainPoint
 *  find the region contain contact point
 * @param b2Vec2 pPoint
 * @return index of region, -1 if not found
 */
char EnemyAI::findRegionContainPoint(b2Vec2 pPoint)
{
    SensorRegion *region;
    for (unsigned char i = 0; i < regionList->count(); ++i) {
        region = (SensorRegion*)regionList->objectAtIndex(i);
        if (region->isContainVector(physicBody->GetLocalPoint(pPoint))) {
            return i;
        }
    }
    return -1;
}

void EnemyAI::findBestRegion()
{
    SensorRegion *region = curRegion;
    SensorRegion *rg = NULL;

    // init priority for regions
    for (unsigned char i = 0; i < regionList->count(); ++i) {
        rg = (SensorRegion*)regionList->objectAtIndex(i);
        if (rg->isLocked) {
            if (rg == topRegion1) {
                curRegion = topRegion2;
            }
            else if (rg == topRegion2) {
                curRegion = topRegion1;
            }
        } else {
            rg->priority = AI_DEFAULT_PRIORITY;
        }
    }

    PhysicObject *trap = NULL;
    unsigned int i = 0;
    int pos = 0;
    int rgNumber = 0;
    int oppSide = DIVIDED_POINTS-1;
    float dangerRate = 0;


    while (i < trapList->count()) {
        // calculate dangerous rate for region contain trap
        trap = (PhysicObject*)trapList->objectAtIndex(i);
        rgNumber = this->findRegionContainPoint(trap->physicBody->GetWorldCenter());
        dangerRate = DIVIDED_POINTS + radios
                - lengthOfVector(this->physicBody->GetLocalPoint(trap->physicBody->GetWorldCenter()));

        if (rgNumber >= 0) {
            // decerease priority region contain trap
            ((SensorRegion*)regionList->objectAtIndex(rgNumber))->priority -= dangerRate;

            if (trap->objType == LAND) {
                continue;
            }

            // opposite side of that region
            oppSide = (i < LEFT_REGION) ? (i + LEFT_REGION) : (i - LEFT_REGION);

            // decrease oppsite side if trap is weapon
            if (trap->objType == STONE || trap->objType == ARROW || trap->objType == JAVELIN) {
                ((SensorRegion*)regionList->objectAtIndex(oppSide))->priority -= dangerRate;

                // select region near opposite side
                if (oppSide > 0) {
                    region = (SensorRegion*)regionList->objectAtIndex(oppSide-1);
                } else {
                    region = (SensorRegion*)regionList->objectAtIndex(oppSide+1);
                }
            } else {
                // select region opposite side
                region = (SensorRegion*)regionList->objectAtIndex(oppSide);
            }

            // decrease priority of two region near that region
            dangerRate /= 2.f;
            pos = i+1;
            if (pos >= DIVIDED_POINTS) {
                pos -= DIVIDED_POINTS;
            }
            ((SensorRegion*)regionList->objectAtIndex(pos))->priority -= dangerRate;
            pos = i-1;
            if (pos < 0) {
                pos += DIVIDED_POINTS;
            }
            ((SensorRegion*)regionList->objectAtIndex(pos))->priority -= dangerRate;

            // choose the temp best region
            if (region->priority > curRegion->priority) {
                curRegion = region;
            }
        }

        ++i;
    }
    // choose the best region
    for (i = 0; i < regionList->count(); ++i) {
        rg = (SensorRegion*)regionList->objectAtIndex(i);
        if (rg->priority > curRegion->priority) {
            curRegion = rg;
        }
    }

}

b2Vec2 EnemyAI::findBestDirection()
{
    float32 x, y;
    if (trapList->count() > 0) {
        this->findBestRegion();
    }
    else if (topRegion1->isLocked) {
        if (this->level == 3) {
            return b2Vec2(-1, 1);
        }
        this->findBestRegion();
    }
    else if (topRegion2->isLocked) {
        if (this->level == 3) {
            return b2Vec2(1, 1);
        }
        this->findBestRegion();
    }
    else {
        int regIndex = regionList->indexOfObject(curRegion);
        if (enemyInstance->sprite->getPositionY() <= enemyInstance->originPos.y
                && regIndex >= LEFT_REGION) {
            regIndex = rand() % LEFT_REGION;
        } else {
            if (timeToChangeDirect > refreshTime) {
#ifdef CCOBJECT_DEBUG
    CCLog("change direct");
#endif
                regIndex = rand() % DIVIDED_POINTS;
                timeToChangeDirect = 0;
            }
        }
        curRegion = (SensorRegion*)regionList->objectAtIndex(regIndex);
    }

    if (curRegion->startPoint.x < curRegion->endPoint.x) {
        x = frandomFromTo(curRegion->startPoint.x, curRegion->endPoint.x, 100);
    } else {
        x = frandomFromTo(curRegion->endPoint.x, curRegion->startPoint.x, 100);
    }

    if (curRegion->startPoint.y < curRegion->endPoint.y) {
        y = frandomFromTo(curRegion->startPoint.y, curRegion->endPoint.y, 100);
    } else {
        y = frandomFromTo(curRegion->endPoint.y, curRegion->startPoint.y, 100);
    }

    // debug
#ifdef CCOBJECT_DEBUG
    CCLog("best direction: (%f, %f)", x, y);
#endif

    return b2Vec2(x, y);
}

void EnemyAI::checkAttackCondition()
{
    if (handler->mHero->mBalloon->isBalloon) {
        this->heroPos = handler->mHero->mBalloon->physicBody->GetWorldCenter();
    } else {
        this->heroPos = handler->mHero->physicBody->GetWorldCenter();
    }
    this->enemyPos = enemyInstance->physicBody->GetWorldCenter();
    this->contactRegion = this->findRegionContainPoint(this->heroPos);
}

void EnemyAI::attackHero()
{
    if (this->contactRegion < LEFT_REGION) {
        enemyInstance->characterFly(this->avoidHero());
    }
}

b2Vec2 EnemyAI::avoidHero()
{
    if (this->contactRegion == UP_REGION-1) {
        topRegion1->isLocked = true;
        topRegion1->priority = AI_DEFAULT_PRIORITY - DIVIDED_POINTS;
    } else {
        topRegion1->isLocked = false;
    }

    if (this->contactRegion == UP_REGION) {
        topRegion2->isLocked = true;
        topRegion2->priority = AI_DEFAULT_PRIORITY - DIVIDED_POINTS;
    } else {
        topRegion2->isLocked = false;
    }
    return this->findBestDirection();
}

void EnemyAI::update(float dt)
{
    timeToAction += dt;
    timeToChangeDirect += dt;

    if (timeToAction > AI_DELAY_TIME && enemyInstance->isControllable) {
        timeToAction = 0;

        b2Vec2 flyDirect;
        if (!enemyInstance->mBalloon->isBalloon) {
            if (enemyInstance->charState == CHARACTER_SWING) {
                if (safeLand) {
                    if ((safeLand->position.y + safeLand->size.height/2.f) >
                            (enemyInstance->sprite->getPositionY() - enemyInstance->sprite->getContentSize().height/2.f
                             )) {
                        // miss landed, random swing
                        safeLand = NULL;
                        flyDirect = b2Vec2(frandomFromTo(-1.f, 1.f, 10), 0);
                    }
                    else if (safeLand->position.x != enemyInstance->sprite->getPositionX()) {
                        // get closer to the land
                        flyDirect = getVectorDirection((safeLand->physicBody->GetPosition().x
                                                       - this->physicBody->GetPosition().x), 0);
                    }
                } else {
                    // random swing
                    flyDirect = b2Vec2(frandomFromTo(-1.f, 1.f, 10), 0);
                }
                enemyInstance->characterFly(flyDirect);
            } else {
                // no longer swing
                safeLand = NULL;
            }
            return;
        }

        // check when saw hero
        if (sawHero) {
            if (handler->mHero->isHide) {
                sawHero = false;
            }
            else if (enemyInstance->sprite->getPositionY() >= enemyInstance->originPos.y) {
                // find positions of hero and enemy
                this->checkAttackCondition();

                // attack hero
                timeToAttack += dt;
                if (timeToAttack > attackDuration) {
                    this->attackHero();
                    timeToAttack = 0;
                    return;
                } else if (this->contactRegion < LEFT_REGION) {
                    enemyInstance->characterFly(this->avoidHero());
                    return;
                }
            }
        }

        // find direction to fly
        flyDirect = this->findBestDirection();
        // check if near deathline
        if (physicBody->GetWorldCenter().y - handler->deathLine <= radios) {
            bool flyUp = true;

            if (handler->landLowestList) {
                LandPhysic *land;
                for (unsigned int i = 0; i < handler->landLowestList->count(); ++i) {
                    land = (LandPhysic*)handler->landLowestList->objectAtIndex(i);

                    if (enemyInstance->sprite->getPositionY() > land->position.y
                            && land->position.x - land->size.width/2.f <
                            enemyInstance->sprite->getPositionX() - enemyInstance->sprite->getContentSize().width/2.f
                            && land->position.x + land->size.width/2.f >
                            enemyInstance->sprite->getPositionX() + enemyInstance->sprite->getContentSize().width/2.f
                            ) {
                        flyUp = false;
                        break;
                    }
                }
            }
            if (flyUp) {
                flyDirect = b2Vec2(flyDirect.x, 1);
            }
        }
        enemyInstance->characterFly(flyDirect);
    }
}

void EnemyAI::removeObject()
{
    if (regionList) {
        regionList->release();
        regionList = NULL;
    }
    if (trapList) {
        trapList->release();
        trapList = NULL;
    }
}
